import {
	appendInitialChild,
	Container,
	createInstance,
	createTextInstance,
	Instance
} from '../../react-dom/src/hostConfig';
import { FiberNode, OffscreenProps } from './fiber';
import { NoFlags, Ref, Update, Visibility } from './fiberFlags';
import {
	HostRoot,
	HostText,
	HostComponent,
	FunctionComponent,
	Fragment,
	ContextProvider,
	OffscreenComponent,
	SuspenseComponent,
	MemoComponent
} from './workTags';
import { popProvider } from './fiberContext';
import { popSuspenseHandler } from './suspenseContext';
import { mergeLanes, NoLanes } from './fiberLanes';

function markUpdate(fiber: FiberNode) {
	fiber.flags |= Update;
}

function markRef(fiber: FiberNode) {
	fiber.flags |= Ref;
}

// completeWork mount 做的事
// 对于Host类型fiberNode：构建离屏DOM树  同时 递归 将 子节 兄弟节点的return属性指向父节点 因为 beginWork阶段的 mount 阶段只 将第一子 child  只向了 父节点
// 标记Update  更新的flag

// 更新和 首次渲染 都做的事 将子元素的 flags 和 子元素的 subtreeFlags 统一冒泡到 父元素的 subtreeFlags 中去
export const completeWork = (wip: FiberNode) => {
	// 递归中的归

	const newProps = wip.pendingProps;
	const current = wip.alternate; // 显示区的fiber

	// 首次渲染 或者没有复用的Fiber completeWork 阶段 创建离屏dom树是从底往上创建的
	switch (wip.tag) {
		case HostComponent:
			if (current !== null && wip.stateNode) {
				// TODO  更新阶段 update且 当前fiber 有对应dom 那就说明是复用的Fiber阶段
				// 1. props是否变化 {onClick: xx} {onClick: xxx}
				// 2. 变了 Update flag
				// className style 所有属性都要判断
				// 如果变了 就打上update 标记
				// 然后 commit流程找到具体的fiber 然后 执行对应的操作
				// 同时将变化的值保存下来 然后更新dom
				// 比如 class 从 a 变成了b 那就 将dom的class 改成b
				//  这里还有做的事 就是将 变化的 props 保存下来 然后 更新dom 上保存 props属性的值 里面哟 回调事件
				// 这样就可以 将 react-dom 的事件系统 和 调度器 结合起来

				// 保存在 fiber.updateQueue  数组中 比较合适
				// 数组偶数是属性名 奇数是变化的 属性值
				// 为了简便 直接调用 update 传入新属性
				// 这里为了简便 直接不对比 props 了 直接赋值给 dom
				// 这里注释 掉 直接标记为需要更新 然后在commit 阶段 处理该标记的更新阶段 再执行updateFiberProps函数
				// updateFiberProps(workInProgress.stateNode, newProps);

				markUpdate(wip);
				// 标记Ref
				if (current.ref !== wip.ref) {
					markRef(wip);
				}
			} else {
				// 首次页面渲染 或者 更新过程中 新加入了节点 节点还没有对应的dom
				// 1. 构建DOM
				// const instance = createInstance(wip.type, newProps);
				// createInstance 传入newProps 属性 保存到dom上  	// 这样就可以 将 react-dom 的事件系统 和 调度器 结合起来
				const instance = createInstance(wip.type, newProps);

				// 2. 将DOM插入到DOM树中 构建离屏dom树
				// 从 最底层 一步一步创建 离屏dom树  然后 最终插入 浏览器
				// 走到这里 说明 当前Fiber 对应的 子Fiber对应的 dom 都创建好了 只需要 递归的把他们 插入父节点就行
				// 然后将 子fiber的 兄弟节点的 return 指向wip
				appendAllChildren(instance, wip);
				wip.stateNode = instance;
				// 标记Ref
				if (wip.ref !== null) {
					markRef(wip);
				}
			}
			//将子fiber 中 有无某个类型的 副作用 冒泡到当前fiber中的subtreeFlags 中去
			bubbleProperties(wip);
			return null;
		case HostText:
			if (current !== null && wip.stateNode) {
				// update
				const oldText = current.memoizedProps?.content;
				const newText = newProps.content;
				if (oldText !== newText) {
					markUpdate(wip);
				}
			} else {
				// 1. 构建DOM
				// 走到这里 说明 子child 肯定没有了 对应的是 文本
				// 只需要 创建对应的dom 和 Fiber关联起来就行 然后等到了 父节点的 completeWork 阶段就会插入 父dom
				const instance = createTextInstance(newProps.content);
				wip.stateNode = instance;
			}
			bubbleProperties(wip);
			return null;
		case HostRoot:
			bubbleProperties(wip);
			return null;
		case FunctionComponent:
		case Fragment:
		case OffscreenComponent:
		case MemoComponent:
			bubbleProperties(wip);
			return null;
		case ContextProvider:
			const context = wip.type._context;
			popProvider(context);
			bubbleProperties(wip);
			return null;
		case SuspenseComponent:
			popSuspenseHandler();

			const offscreenFiber = wip.child as FiberNode;
			const isHidden = offscreenFiber.pendingProps.mode === 'hidden';
			const currentOffscreenFiber = offscreenFiber.alternate;
			if (currentOffscreenFiber !== null) {
				const wasHidden = currentOffscreenFiber.pendingProps.mode === 'hidden';

				if (isHidden !== wasHidden) {
					// 可见性变化
					offscreenFiber.flags |= Visibility;
					bubbleProperties(offscreenFiber);
				}
			} else if (isHidden) {
				// mount时hidden
				offscreenFiber.flags |= Visibility;
				bubbleProperties(offscreenFiber);
			}
			bubbleProperties(wip);
			return null;
		default:
			if (true) {
				console.warn('未处理的completeWork情况', wip);
			}
			break;
	}
};

// 举例1 : 这种情况 div1 是当前Fiber 那么 找到appFiber.child.stateNode 插入就行
// const app = <div>1</div>
// <div1>
// <app></app> appFiber
// </div1>

// 举2 : 这种情况 div1 是当前Fiber 那么 找到appFiber.child.child.stateNode 插入就行
// app2 = <div></div>
// const app = <app2></app2>
// <div1>
// <app></app> appFiber
// </div1>

// 举例3 : 这种情况 div1 是当前Fiber 那么 找到 span.stateNode插入就行  然后将 span.sibling.stateNode插入就行
// const app = <div>1</div>
// <div1>
// <span><span> <h1></h1>
// </div1>

// 因为这是一个 公用方法 可能情况 因为 class 和函数组件 等没有对应的 dom 所以碰到 需要 再往下找
// 这个方法的作用是 如果 子节点 和子节点的兄弟 是 dom 类型的 Fiber 那就将他们的dom 插入 parent
// 如果 是函数Fiber或 类Fiber 因为没有对应dom 节点那就继续往下找n层  对应的dom 插入 parent 就行

function appendAllChildren(parent: Container | Instance, wip: FiberNode) {
	// wip当前Fiber
	let node = wip.child;

	while (node !== null) {
		// 这里是 if else if 关系 node 已经有对应dom了 就插入 没有则 往下找
		// 然后 再去处理 兄弟节点
		if (node.tag === HostComponent || node.tag === HostText) {
			// 这里说明  子节点 有对应dom 就子dom 插入 parent就行
			// 因为 completeWork 是从下往上走的 所以 既然 这里已经有了dom 说明 是dom树或者单个dom
			appendInitialChild(parent, node?.stateNode);
		} else if (node.child !== null) {
			// 没有对应dom 往下再找
			// 孙Fiber 不为null
			node.child.return = node; // 孙节点的return 指向 子节点
			node = node.child; // 将孙节点 作为当前节点取 递归
			continue;
		}

		// 来到这这里表示 孙节点为null 走到头了
		// 递归 开始 归阶段了
		// 当前node 等于 wip 说明 归到头了 直接return
		if (node === wip) {
			return;
		}

		// 孙节点为null 的话取 处理 兄弟节点
		while (node.sibling === null) {
			if (node.return === null || node.return === wip) {
				// 如果 归到头了 或者 倒数第二层了 且还 没有兄弟节点 直接return
				return;
			}
			// 归阶段 直接 往上走
			node = node?.return;
		}

		// 在begin阶段 第一个子节点已经指向了 父节点 所以不需要再指向了
		// 这里只需要将 兄弟节点的return 指向 父节点

		// 这里继续 往 兄弟节点 递
		// node 的 兄弟节点不为null  node的兄弟的节点 = node 的 父节点
		node.sibling.return = node.return;
		// 把当前节点设置为兄弟 节点 然后取出来
		node = node.sibling;
	}
}

//将子fiber 中 有无某个类型的 副作用 冒泡到当前fiber中的subtreeFlags 中去
// 该函数作用 将 子fiber的 副作用 合集 冒泡到 自己的subtreeFlags 中去
// 这样就可以知道 子fiber树中 有无增删改移中的某个 或都没有 都没有就不需要遍历
function bubbleProperties(wip: FiberNode) {
	let subtreeFlags = NoFlags;
	let child = wip.child;
	let newChildLanes = NoLanes;

	// subtreeFlags存放 所以子fiber 中的 所以类型副作用flags 如果每个位上都是1代表 增删改移 都存在
	// 那么后续只需要 判断 subtreeFlags 就可以知道子fiber 有没有副作用操作 就不需要一个个遍历了
	while (child !== null) {
		// 将子fiber的辅作用
		subtreeFlags |= child.subtreeFlags; // 000 ｜= 010 就变成 010 收集子Fiber 的child的所有副作用类型
		subtreeFlags |= child.flags; // 将子fiber 自己的 flags 类型也收集一下

		// child.lanes child.childLanes
		newChildLanes = mergeLanes(
			newChildLanes,
			mergeLanes(child.lanes, child.childLanes)
		);

		child.return = wip;
		// 递归取收集子fiber的 兄弟节点的 副操作类型合集
		child = child.sibling;
	}
	wip.subtreeFlags |= subtreeFlags;
	wip.childLanes = newChildLanes;
}
